
#include <BKeyEvent>
#include <BGridLayout>
#include <BHBoxLayout>
#include <BVBoxLayout>
#include <BFile>
#include <BReadWrite>
#include <BPainter>
#include <BStyle>
#include <BFont>
#include <BMessageBox>
#include <BMouseEvent>
#include <BApplication>

#include "ScriptBrowser.h"
#include "SourceEdit.h"

SourceEdit::SourceEdit(BWidget* parent) : BTextEdit(parent)
{
	SourceFormat* format = new SourceFormat();
	this->setFormat(format);

	_promptBox = new PromptBox();
	_promptBox->hide();
	this->addChild(_promptBox);

	connect(this, Signal_Flushed, &SourceEdit::slotFlushed);
	connect(this, Signal_AssetChanged, &SourceEdit::slotAssetChanged);
	connect(this, Signal_TextChanged, &SourceEdit::slotTextChanged);
	connect(this, Signal_TextEdited, &SourceEdit::slotTextEdited);
}
SourceEdit::~SourceEdit()
{

}

void SourceEdit::analyseSource()
{
	BSource* source = this->asset();
	BScript* script = source->script();
	_messagMap.clear();
	connect(script, Signal_Message, &SourceEdit::slotScriptMessage);
	const BExpHolderArray& document = source->analyse(this->text());
	disconnect(script);

	const BString& fileName = source->name();
	SourceFormat* format = (SourceFormat*)this->format();
	format->clear();
	for (int i = 0; i < document.size(); i++)
	{
		BExp* exp = document[i];
		if (exp->info.beginWith(fileName))
		{
			SourceFormat::Item* item = format->insert(exp);
			auto it = _messagMap.find(exp->info);
			if (it.valid())
			{
				item->error = true;
				item->errorText = *it;
			}
		}
	}
	_messagMap.clear();
	_chunkTree.clear();
	for (int i = 0; i < document.size(); i++)
	{
		createChunk(document[i], ChunkTree::iterator());
	}
}

void SourceEdit::freshFormat(const BExpHolderArray& document)
{
	BSource* source = this->asset();
}
void SourceEdit::drawErrorFlag(BPainter& painter, SourceFormat::Item* item)
{
	const BImage* wavyImage = painter.font()->image('~');
	if (wavyImage)
	{
		int count = item->rect.width() / wavyImage->width();
		if (count < 1)
			count = 1;
		BRect lineRect = this->lineRect(item->line);
		BPoint pos(item->rect.left(), lineRect.bottom());
		pos.x() += (item->rect.width() - wavyImage->width() * count) / 2;
		painter.setColor(168, 0, 0);
		for (int i = 0; i < count; i++)
		{
			painter.drawImage(pos, wavyImage->size(), wavyImage);
			pos.x() += wavyImage->width();
		}
	}
}

void SourceEdit::createChunk(BExp* exp, ChunkTree::iterator it)
{
	BSource* source = this->asset();
	if (exp->info.beginWith(source->name()))
	{
		if (_chunkTree.search(exp).valid())
			return;
		if (exp->syntax == Syntax_Note && exp->value.valid())
		{
			if (it.valid())
				it = it.append();
			else
				it = _chunkTree.append();
			BString beginStr = exp->info.word(exp->info.find(':') + 1);
			it->begin = beginStr.toInt();
			it->end = exp->value;
			it->exp = exp;
		}
		if (exp->subs.size() > 1 && exp->subs.first()->syntax == Syntax_Begin && exp->subs.last()->syntax == Syntax_End)
		{
			if (it.valid())
				it = it.append();
			else
				it = _chunkTree.append();
			BString beginStr = exp->info.word(exp->info.find(':') + 1);
			it->begin = beginStr.toInt();
			BExp* endExp = exp->subs.last();
			BString endStr = endExp->info.word(endExp->info.find(':') + 1);
			it->end = endStr.toInt();
			it->exp = exp;
		}
		for (int i = 0; i < exp->params.size(); i++)
		{
			createChunk(exp->params[i], it);
		}
		for (int i = 0; i < exp->subs.size(); i++)
		{
			createChunk(exp->subs[i], it);
		}
	}
}
void SourceEdit::freshChunk(ChunkTree::iterator& it)
{
	const BRect& beginRect = this->lineRect(it->begin);
	const BRect& endRect = this->lineRect(it->end);
	it->head.set(_chunkArea.left(), beginRect.top(), _chunkArea.width(), beginRect.height());
	it->head %= 60;
	it->tail.set(_chunkArea.left(), endRect.top(), _chunkArea.width(), endRect.height());
	it->tail %= 60;
	it->rect = it->head;
	if (it->visible)
	{
		it->rect.expand(it->tail);
	}
	for (int i = 0; i < it.count(); i++)
	{
		auto sub = it.seek(i);
		freshChunk(sub);
	}
}
void SourceEdit::foldedChunk(ChunkTree::iterator& it)
{
	for (int i = it->begin + 1; i <= it->end; i++)
	{
		this->setLineVisible(i, it->visible);
	}
	if (it->visible)
	{
		for (auto sub = it.begin(); sub < it.end(); sub++)
		{
			foldedChunk(sub);
		}
	}
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkUnder(const BPoint& point)
{
	for (auto it = _chunkTree.begin(); it.valid(); ++it)
	{
		auto res = chunkUnder(it, point);
		if (res.valid())
			return res;
	}
	return _chunkTree.end();
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkUnder(ChunkTree::iterator& it, const BPoint& point)
{
	if (it->rect.contain(point))
	{
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			auto res = chunkUnder(sub, point);
			if (res.valid())
				return res;
		}
		return it;
	}
	return it.end();
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkOfLine(int line)
{
	for (auto it = _chunkTree.begin(); it.valid(); ++it)
	{
		auto res = chunkOfLine(it, line);
		if (res.valid())
			return res;
	}
	return _chunkTree.end();
}
SourceEdit::ChunkTree::iterator SourceEdit::chunkOfLine(ChunkTree::iterator& it, int line)
{
	Chunk& chunk = *it;
	if (line > it->begin && line < it->end)
	{
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			auto res = chunkOfLine(sub, line);
			if (res.valid())
				return res;
		}
		return it;
	}
	return it.end();
}
void SourceEdit::drawChunk(BPainter& painter, ChunkTree::iterator& it)
{
	if (it == _hoveredChunk)
	{
		painter.setColor(BColor(128, 156, 200));
		painter.fillRect(it->rect);
	}
	painter.setColor(BColor(28, 56, 128));
	painter.drawRect(it->head);
	if (it->visible)
	{
		if (_minusImage)
			painter.drawImage(it->head % 80, _minusImage, true);
		else
			painter.drawGraph(it->head % 80, Graph_Minus);
		painter.drawLine(it->head.bottomCenter(), it->tail.center());
		painter.drawLine(it->tail.center(), it->tail.rightCenter());
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			drawChunk(painter, sub);
		}
	}
	else
	{
		if (_plusImage)
			painter.drawImage(it->head % 80, _plusImage, true);
		else
			painter.drawGraph(it->head % 80, Graph_Plus);
	}
}

void SourceEdit::slotAssetChanged(BObject* object, const BValue& value)
{
	BSource* source = value;
	_promptBox->setSource(source);
	this->setText(source->text());
	const BString& name = source->name();
	BScript* script = source->script();
	for (int i = 0; i < script->breakpointCount(); i++)
	{
		const BString& breakpoint = script->breakpoint(i);
		if (breakpoint.beginWith(name))
		{
			int pos = breakpoint.find(':');
			int number = breakpoint.substr(pos + 1, breakpoint.size()).toInt();
			if (number >= 0)
			{
				int index = _breakLines.find(number);
				if (_breakLines.check(index))
					_breakLines.remove(index, 1);
				else
					_breakLines.insert(number);
			}
		}
	}
}
void SourceEdit::slotTextChanged(BObject* object, const BValue& value)
{
	analyseSource();
}
void SourceEdit::slotTextEdited(BObject* object, const BValue& value)
{
	analyseSource();
}
void SourceEdit::slotFlushed(BObject* object, const BValue& value)
{
	if (this->query("code-rects", _codeRectsValue))
	{
		SourceFormat* format = (SourceFormat*)this->format();
		for (auto it = format->itemMap.begin(); it.valid(); it++)
		{
			SourceFormat::Item* item = *it;
			int min = item->range.min();
			int max = item->range.max();
			const BRectArray* codeRects = _codeRectsValue;
			item->rect = codeRects->at(min);
			if (item->exp && item->exp->syntax == Syntax_Note && item->exp->value.is<int>())
			{
				int line = item->line;
				for (int i = min + 1; i < max; i++)
				{
					const BRect& rect = codeRects->at(i);
					if (rect.center().y() > item->rect.bottom())
						if (!this->lineVisible(++line))
							break;
					item->rect.expand(codeRects->at(i));
				}
			}
			else
			{
				for (int i = min + 1; i < max; i++)
				{
					item->rect.expand(codeRects->at(i));
				}
			}
		}
	}
	for (int i = 0; i < _chunkTree.count(); i++)
	{
		auto it = _chunkTree.seek(i);
		freshChunk(it);
	}
}

void SourceEdit::slotScriptMessage(BObject* object, const BValue& value)
{
	const BString& text = value;
	int infoEnd = text.find(')');
	if (infoEnd < text.size())
	{
		BString info;
		text.substr(info, 0, infoEnd + 1);
		BString& message = _messagMap[info];
		text.substr(message, text.find(':', infoEnd) + 1, text.size());
	}
}

void SourceEdit::updateEvent(const BEvent& event)
{
	BTextEdit::updateEvent(event);
	if (_promptBox->item() && _promptBox->visible() == false)
	{
		if (event.time() - _hoverTime > 1.0)
		{
			_promptBox->setSize(_promptBox->sizeHint());
			_promptBox->setVisible(true);
		}
	}
}
void SourceEdit::showEvent(const BEvent& event)
{
	BTextEdit::showEvent(event);
}
void SourceEdit::closeEvent(const BEvent& event)
{
	BTextEdit::closeEvent(event);
}

void SourceEdit::resizeEvent(const BEvent& event)
{
	BTextEdit::resizeEvent(event);
	BRect rect = this->rect(Part_Left);
	rect.move(rect.width(), 0);
	int half = this->margin(Part_Left) / 2;
	_breakArea.set(rect.x(), rect.y(), half, rect.height());
	_chunkArea.set(_breakArea.right(), rect.y(), half, rect.height());
}

void SourceEdit::keyUpEvent(const BKeyEvent& keyEvent)
{
	BTextEdit::keyUpEvent(keyEvent);
	if (keyEvent.key() == Key_Enter)
	{
		BValue inputLineValue;
		this->query("input-line", inputLineValue);
		int line = inputLineValue;
		const BString& prev = this->lineText(line - 1);
		int tab = 0;
		for (int i = 0; i < prev.size(); i++)
		{
			if (prev[i] != '\t')
				break;
			tab++;
		}
		BString text = this->lineText(line);
		text.prepend('\t', tab);
		this->setLineText(line, text);
	}
}

void SourceEdit::styleEvent(const BEvent& event)
{
	BTextEdit::styleEvent(event);
	const BFont* font = this->font();
	if (const BStyle* style = event.value())
	{
		_minusImage = style->image("minus");
		if (_minusImage.empty() && font)
			_minusImage = font->image('-');
		_plusImage = style->image("plus");
		if (_plusImage.empty() && font)
			_plusImage = font->image('+');
	}
	if (font)
	{
		this->setMargin(Part_Left, font->size() * 2);
	}
	else
	{
		this->setMargin(Part_Left, 32);
	}
}
void SourceEdit::freshEvent(const BEvent& event)
{
	if (const BFont* font = this->font())
	{
		BString numberStr(this->lineCount());
		int left = font->textSize(numberStr).width();
		if (left < font->size())
			left = font->size();
		this->setPerch(Part_Left, left);
	}
	BTextEdit::freshEvent(event);
}
void SourceEdit::paintEvent(const BEvent& event)
{
	BTextEdit::paintEvent(event);
	BPainter painter(this);

	if (const BFont* font = painter.font())
	{
		BValue value;
		if (this->query("line-range", value))
		{
			BRange lineRange = value;
			BString numberText;
			const BRect& leftRect = this->rect(Part_Left);
			int x = leftRect.x();
			int w = leftRect.width();
			painter.setColor(Color_Text);
			for (int i = lineRange.min(); i < lineRange.max(); i++)
			{
				if (this->lineVisible(i))
				{
					const BRect& rect = this->lineRect(i);
					numberText.reset();
					numberText << i;
					painter.drawText(x, rect.y(), w, rect.height(), numberText, Align_Center);
				}
			}
		}
		if (this->query("input-line", value))
		{
			int line = value;
			if (line >= 0)
			{
				const BRect& inputRect = this->lineRect(line);
				painter.setColor(128, 128, 128, 128);
				painter.setLineWidth(2);
				painter.drawRect(inputRect);
			}
			painter.setLineWidth(1);
		}
	}

	for (int i = 0; i < _chunkTree.count(); i++)
	{
		auto it = _chunkTree.seek(i);
		drawChunk(painter, it);
	}

	painter.setColor(128, 128, 128, 64);
	painter.fillRect(_breakArea);

	SourceFormat* format = (SourceFormat*)this->format();
	for (auto it = format->itemMap.begin(); it.valid(); it++)
	{
		SourceFormat::Item* item = *it;
		if (item->error)
		{
			drawErrorFlag(painter, item);
		}
		//if (this->lineVisible(item->line))
		//{
		//	painter.setColor(168, 168, 0);
		//	painter.drawRect(item->rect);
		//}
	}

	painter.setColor(200, 128, 128);
	for (int i = 0; i < _breakLines.size(); i++)
	{
		int line = _breakLines[i];
		BRect rect = this->lineRect(line);
		rect.left() = _breakArea.left();
		rect.right() = _breakArea.right();
		rect.scale(rect.center(), 0.8);
		painter.drawGraph(rect, Graph_Circle_Fill);
	}
}

void SourceEdit::mousePressEvent(const BMouseEvent& mouseEvent)
{
	BPoint point = mouseEvent.pos();
	if (_breakArea.contain(point))
	{
		int line = this->lineAt(point);
		if (line >= 0)
		{
			int index = _breakLines.find(line);
			if (_breakLines.check(index))
			{
				_breakLines.remove(index, 1);
				emit("remove-breakpoint", line);
			}
			else
			{
				_breakLines.insert(line);
				emit("create-breakpoint", line);
			}
			this->fresh();
		}
	}
	if (_hoveredChunk.valid())
	{
		_hoveredChunk->visible = !_hoveredChunk->visible;
		foldedChunk(_hoveredChunk);
	}
	BTextEdit::mousePressEvent(mouseEvent);
}
void SourceEdit::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	BTextEdit::mouseReleaseEvent(mouseEvent);
}
void SourceEdit::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	BTextEdit::mouseMoveEvent(mouseEvent);
	BPoint pos = mouseEvent.pos();
	if (_breakArea.contain(pos))
	{
		this->setCursor(Cursor_Hand);
	}
	SourceFormat::Item* hoverItem = 0;
	SourceFormat* format = (SourceFormat*)this->format();
	for (auto it = format->itemMap.begin(); it.valid(); it++)
	{
		SourceFormat::Item* item = *it;
		if (!item->exp)
			continue;
		if (!this->lineVisible(item->line))
			continue;
		if (!item->rect.contain(pos))
			continue;
		hoverItem = item;
		break;
	}
	if (_promptBox->item() != hoverItem)
	{
		if (hoverItem)
		{
			pos.y() = hoverItem->rect.bottom();
			_promptBox->setPos(pos);
		}
		_promptBox->hide();
		_promptBox->setItem(hoverItem);
		_hoverTime = mouseEvent.time();
	}
	ChunkTree::iterator hoveredChunk = _chunkTree.end();
	if (_chunkArea.contain(pos))
	{
		hoveredChunk = chunkUnder(pos);
	}
	if (_hoveredChunk != hoveredChunk)
	{
		_hoveredChunk = hoveredChunk;
		this->fresh();
	}
}
